﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading;
using System.Globalization;
using System.Diagnostics;
using System.Xml;
using System.Reflection;

namespace GPCodeConjurer
{


	public class GraphicsPathEditor : Control
	{
		public GraphicsPathEditor()
		{
			this.Cursor = Cursors.Arrow;
			this.DoubleBuffered = true;
			this.GrabHandleSize = 8;
			this.Size = new Size(100, 100);
			this.LineJoin = LineJoin.Miter;
			this.MiterLimit = 1.0f;
			this.IndicateObjects = false;
			this.FillColor = Color.Black;
			this.DrawColor = Color.Black;
			this.IndicationColor = Color.FromArgb(128, Color.White);
			this.vScrollBar = new VScrollBar() { Margin = new Padding(10, 0, 0, 0) };
			this.hScrollBar = new HScrollBar() { Margin = new Padding(100, 0, 0, 1) };
			this.hScrollBar.ValueChanged += (o, ev) =>
				{
					this.Invalidate();
				};
			this.vScrollBar.ValueChanged += (o, ev) =>
			{
				this.Invalidate();
			};
			this.Controls.Add(this.vScrollBar);
			this.Controls.Add(this.hScrollBar);
		}

		private VScrollBar vScrollBar = null;
		private HScrollBar hScrollBar = null;
		

		public int GrabHandleSize { get; set; }

		public event EventHandler SelectionChanged = null;


		public event EventHandler NodeListCleared = null;

		public event EventHandler<NodeListChangedEventArgs> NodeRemoved = null;

		public event EventHandler<NodeListChangedEventArgs> NodeAdded = null;

		public event EventHandler<SelectedNodePropertyChanged> SelectedNodePropertyChanged = null;





		[NonSerialized]
		private List<Node> nodes = new List<Node>();
		public List<Node> Nodes
		{
			get
			{
				return this.nodes;
			}
		}


		public bool FillPath { get; set; }
		public float LineWidth { get; set; }
		public FillMode FillMode { get; set; }
		public LineJoin LineJoin { get; set; }
		public float MiterLimit { get; set; }
		public bool IndicateObjects { get; set; }
		public Color FillColor { get; set; }
		public Color DrawColor { get; set; }
		public Color IndicationColor { get; set; }


		internal float zoom = 1.0f;
		public float Zoom
		{
			get
			{
				return this.zoom;
			}
			set
			{
				if (this.zoom != value && !this.DesignMode)
				{
					this.zoom = value;
					this.Invalidate();
				}
			}
		}


		protected Node CopyNode { get; set; }





		public XmlDocument Serialize()
		{
			XmlDocument xmlDoc = new XmlDocument();
			var root = xmlDoc.AppendChild(xmlDoc.CreateElement("GPCC")) as XmlElement;
			root.SetAttribute("version", this.GetType().Assembly.GetName().Version.ToString());

			// Serialize canvas settings.
			var canvas = root.AppendChild(xmlDoc.CreateElement("Canvas")) as XmlElement;
			canvas.AppendElementChild("FillMode", this.FillMode.ToString());
			canvas.AppendElementChild("FillPath", this.FillPath.ToString(CultureInfo.InvariantCulture).ToLower());
			canvas.AppendElementChild("FillColor", ColorTranslator.ToHtml(this.FillColor).ToLower());
			canvas.AppendElementChild("LineWidth", this.LineWidth.ToString(CultureInfo.InvariantCulture).ToLower());
			canvas.AppendElementChild("IndicateObjects", this.IndicateObjects.ToString(CultureInfo.InvariantCulture).ToLower());


			var nodes = (XmlElement)root.AppendChild(xmlDoc.CreateElement("Nodes"));
			foreach (var n in this.nodes)
			{
				nodes.AppendChild(n.Serialize(xmlDoc));
			}

			return xmlDoc;
		}


		public void Deserialize(XmlDocument xmlDoc)
		{
			this.Clear();


			// Deserialize the canvas.
			this.FillMode = xmlDoc.SelectEnum<FillMode>("//Canvas/FillMode", FillMode.Alternate);
			this.FillPath = xmlDoc.SelectBool("//Canvas/FillPath", false);
			this.FillColor = xmlDoc.SelectColor("//Canvas/FillColor", Color.Black);
			this.LineWidth = xmlDoc.SelectFloat("//Canvas/LineWidth", 1f);
			this.IndicateObjects = xmlDoc.SelectBool("//Canvas/IndicateObjects", false);


			// Deserialize nodes.
			foreach (XmlNode xmlNode in xmlDoc.SelectNodes("GPCC/Nodes/*"))
			{
				Node node = null;
				switch (xmlNode.Name)
				{
					case "Line":
						node = new Line();
						break;
					case "Ellipse":
						node = new Ellipse();
						break;
					case "Arc":
						node = new Arc();
						break;
				}

				if (node != null)
				{
					node.Deserialize(xmlNode as XmlElement);
					node.RelocateGrabHandles();
					this.AddNode(node);
				}
			}
		}


		public Image ExportToImage()
		{
			const int PADDING = 10;
			Bitmap img = new Bitmap(
				Convert.ToInt32(this.Extent.Width + PADDING + this.LineWidth),
				Convert.ToInt32(this.Extent.Height + PADDING + this.LineWidth)
			);
			using (Graphics graphics = Graphics.FromImage(img))
			{
				graphics.TranslateClip(PADDING + this.LineWidth / 2, PADDING + this.LineWidth / 2);
				graphics.SmoothingMode = SmoothingMode.HighQuality;
				this.drawNodes(graphics);	
			}
			return img;
		}




		public void RemoveNode(Node node)
		{
			this.nodes.Remove(node);
			if (this.selectedNode == node)
			{
				this.SelectedNode = null;
			}
			if (this.NodeRemoved != null)
				this.NodeRemoved(this, new NodeListChangedEventArgs(node));
			this.Invalidate();
		}


		public void AddNode(Node node)
		{
			if (!this.nodes.Contains(node))
			{
				node.PropertyChanged += (o, ev) =>
					{
						if (o == this.selectedNode && this.SelectedNodePropertyChanged != null)
						{
							this.SelectedNodePropertyChanged(this, new SelectedNodePropertyChanged((Node)o));
						}
					};
				this.nodes.Add(node);
				node.Editor = this;
				this.Invalidate();
				if (this.NodeAdded != null)
					this.NodeAdded(this, new NodeListChangedEventArgs(node));
			}
		}


		protected override void OnCreateControl()
		{
			base.OnCreateControl();


		}


		protected override void OnPaintBackground(PaintEventArgs e)
		{
			base.OnPaintBackground(e);

			var rect = this.ClientRectangle;
			rect.Width -= 1;
			rect.Height -= 1;
			e.Graphics.FillRectangle(Brushes.White, rect);
		}




		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint(e);


			// Adjust the scrollbars' statuses
			var extent = this.Extent;
			Size clientSize = new Size(
				this.Width - 10 - ((this.vScrollBar.Visible) ? this.vScrollBar.Width : 0),
				this.Height - 10 - ((this.hScrollBar.Visible) ? this.hScrollBar.Height : 0)
				);
			if (extent.Width <= clientSize.Width && this.hScrollBar.Value == 0)
			{
				this.hScrollBar.Visible = false;
				this.hScrollBar.Enabled = false;
			}
			else
			{
				this.hScrollBar.Visible= true;
				this.hScrollBar.Enabled = true;
				this.hScrollBar.SuspendLayout();
				this.hScrollBar.SmallChange = clientSize.Width / 20;
				this.hScrollBar.Maximum = Convert.ToInt32(Math.Max(extent.Width, clientSize.Width + this.hScrollBar.Value));
				this.hScrollBar.LargeChange = Convert.ToInt32(clientSize.Width);

				//Debug.WriteLine(string.Format("{0} -> {3} <- {1} : LC = {2} : ClientSize = {4}", this.hScrollBar.Minimum, this.hScrollBar.Maximum, this.hScrollBar.LargeChange, this.hScrollBar.Value, clientSize));
				this.hScrollBar.ResumeLayout();
			}
			if (extent.Height <= clientSize.Height && this.vScrollBar.Value == 0)
			{
				this.vScrollBar.Visible = false;
				this.vScrollBar.Enabled = false;
			}
			else
			{
				this.vScrollBar.Visible = true;
				this.vScrollBar.Enabled = true;
				this.vScrollBar.SuspendLayout();
				this.vScrollBar.SmallChange = clientSize.Width / 20;
				this.vScrollBar.Maximum = Convert.ToInt32(Math.Max(extent.Height, clientSize.Height + this.vScrollBar.Value));
				this.vScrollBar.LargeChange = Convert.ToInt32(clientSize.Height); 
				this.vScrollBar.ResumeLayout();
			}


			// save state before adding transformations.
			var state = e.Graphics.Save();
			e.Graphics.TranslateTransform(-this.hScrollBar.Value, -this.vScrollBar.Value);
			e.Graphics.ScaleTransform(this.Zoom, this.Zoom);

			if (!this.DesignMode)
			{
				e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
				this.drawNodes(e.Graphics);

	/*			using (GraphicsPath gp = new GraphicsPath())
				{
					gp.FillMode = this.FillMode;
					foreach (var node in this.Nodes)
					{
						if (node.StartFigure)
							gp.StartFigure();
						node.DrawPath(gp);
						if (node.CloseFigure)
							gp.CloseFigure();
					}
					if (this.drawNode != null)
					{
						if (this.drawNode.StartFigure)
							gp.StartFigure();
						this.drawNode.DrawPath(gp);
						if (this.drawNode.CloseFigure)
							gp.CloseFigure();
					}

					if (this.FillPath)
					{
						Brush fillBrush = new SolidBrush(this.FillColor);
						e.Graphics.FillPath(fillBrush, gp);
						fillBrush.Dispose();
					}
					using (var pen = new Pen(this.DrawColor, this.LineWidth))
					{
						pen.LineJoin = this.LineJoin;
						pen.MiterLimit = this.MiterLimit;
						e.Graphics.DrawPath(pen, gp);
					}

				}*/


				// indicate nodes on the canvas.
				if (this.IndicateObjects)
				{
					using (var pen = new Pen(this.IndicationColor, 0f))
					{
						foreach (var node in this.nodes)
						{
							using (var gp = new GraphicsPath())
							{
								node.DrawPath(gp);
								e.Graphics.DrawPath(pen, gp);
								if (gp.PointCount > 0)
								{
									var firstPt = gp.PathPoints[0];
									var lastPt = gp.PathPoints[gp.PointCount - 1];
									using (var brush = new SolidBrush(this.IndicationColor))
									{
										const float INDICATESIZE = 2;
										e.Graphics.FillEllipse(brush, RectangleF.FromLTRB(firstPt.X - INDICATESIZE, firstPt.Y - INDICATESIZE, firstPt.X + INDICATESIZE, firstPt.Y + INDICATESIZE));
										e.Graphics.FillEllipse(brush, RectangleF.FromLTRB(lastPt.X - INDICATESIZE, lastPt.Y - INDICATESIZE, lastPt.X + INDICATESIZE, lastPt.Y + INDICATESIZE));
									}
								}
							}
						}
					}
				}

/*
				if (this.drawNode != null)
				{
					using (GraphicsPath gp = new GraphicsPath())
					{
						this.drawNode.DrawPath(gp);
						var bounds = gp.GetBounds();
						Rectangle rect = Rectangle.FromLTRB(
							this.lastMouseDownPoint.X,
							this.lastMouseDownPoint.Y,
							Control.MousePosition.X,
							Control.MousePosition.Y);
						if (rect.Width > 0 && rect.Height > 0)
						{
							e.Graphics.DrawPath(Pens.Red, gp);
						}
					}
					return;
				}
				*/

				if (this.selectedNode != null)
				{
					var rect = this.selectedNode.Rect.Absolute();
					ControlPaint.DrawSelectionFrame(e.Graphics, true, Rectangle.Inflate(rect, GrabHandleSize, GrabHandleSize), Rectangle.Inflate(rect, 0, 0), Color.Black);
					this.selectedNode.DrawGrabHandles(e.Graphics);
				}
				else if (this.drawNode != null)
				{
					var rect = this.drawNode.Rect.Absolute();
					ControlPaint.DrawSelectionFrame(e.Graphics, true, Rectangle.Inflate(rect, GrabHandleSize, GrabHandleSize), Rectangle.Inflate(rect, 0, 0), Color.Black);
					//this.selectedNode.DrawGrabHandles(e.Graphics);
				}

				e.Graphics.Restore(state);

			}


			if (this.vScrollBar.Visible & this.hScrollBar.Visible)
			{
				e.Graphics.FillRectangle(SystemBrushes.Control, new Rectangle(this.Width - this.vScrollBar.Width, this.Height - this.hScrollBar.Height, this.vScrollBar.Width, this.hScrollBar.Height));
			}


			// Draw border.
			if (true)	// REFACTOR! Add Border property.
			{
				var rect = this.ClientRectangle;
				rect.Width -= 1;
				rect.Height -= 1;
				e.Graphics.DrawRectangle(Pens.Gray, rect);
			}
		}



		private void drawNodes(Graphics graphics)
		{
			using (GraphicsPath gp = new GraphicsPath())
			{
				gp.FillMode = this.FillMode;
				foreach (var node in this.Nodes)
				{
					if (node.StartFigure)
						gp.StartFigure();
					node.DrawPath(gp);
					if (node.CloseFigure)
						gp.CloseFigure();
				}
				if (this.drawNode != null)
				{
					if (this.drawNode.StartFigure)
						gp.StartFigure();
					this.drawNode.DrawPath(gp);
					if (this.drawNode.CloseFigure)
						gp.CloseFigure();
				}

				if (this.FillPath)
				{
					Brush fillBrush = new SolidBrush(this.FillColor);
					graphics.FillPath(fillBrush, gp);
					fillBrush.Dispose();
				}
				using (var pen = new Pen(this.DrawColor, this.LineWidth))
				{
					pen.LineJoin = this.LineJoin;
					pen.MiterLimit = this.MiterLimit;
					graphics.DrawPath(pen, gp);
				}
			}

		}




		private Node selectedNode = null;
		public Node SelectedNode
		{
			get
			{
				return this.selectedNode;
			}
			set
			{
				if (this.selectedNode != value)
				{
					// unselect previous
					if (this.selectedNode != null)
					{
						this.selectedNode.selected = false;
					}

					// select it
					this.selectedNode = value;
					if (this.selectedNode != null)
					{
						this.selectedNode.selected = true;
					}

					// repaint
					this.Invalidate();

					if (this.SelectionChanged != null)
						this.SelectionChanged(this, new EventArgs());
				}
			}
		}


		private bool mouseDownOnSelectedFrame = true;
		private Point selectedFrameClickOffset = Point.Empty;

		private GrabHandle focusHandle = null;
		private HandleMoveBehavior focusHandleMoveBehaviour = HandleMoveBehavior.All;
		private Point mouseDownOffset = Point.Empty;
		private Point lastMouseDownPoint = Point.Empty;


		[NonSerialized]
		private Node drawNode = null;
		public Node DrawNode
		{
			get
			{
				return drawNode;
			}
			set
			{
				this.drawNode = value;
				if (this.drawNode == null)
				{
					this.Cursor = Cursors.Arrow;
				}
				else
				{
					this.Cursor = Cursors.Cross;
				}
			}
		}




		protected override void OnMouseDown(MouseEventArgs e)
		{
			base.OnMouseDown(e);

			if (this.DesignMode)
				return;

			if (e.Button == MouseButtons.Left)
			{
				var clickPt = e.Location;
				clickPt.Offset(this.hScrollBar.Value, this.vScrollBar.Value);
				this.lastMouseDownPoint = clickPt;


				if (this.drawNode != null)
				{
					this.SelectedNode = null;
					return;
				}
				


				// hit test selction frame.
				if (this.selectedNode != null)
				{
					// Hit test grabhandles.
					var handle = this.selectedNode.GetHandleAtPoint(clickPt);
					if (handle != null)
					{
						this.focusHandle = handle;
						this.focusHandleMoveBehaviour = handle.MoveBehavior;
						this.mouseDownOffset = Point.Subtract(handle.Location, new Size(e.Location));
						return;
					}

					if (this.selectedNode.PointIsOnSelectionFrame(clickPt))
					{
						// Yep, clicked on the frame.
						this.mouseDownOnSelectedFrame = true;
						this.selectedFrameClickOffset = new Point(this.selectedNode.Rect.X - clickPt.X, this.selectedNode.Rect.Y - clickPt.Y);
						return;
					}
					/*
					var rect = this.selectedNode.Rect.Absolute();
					if (Rectangle.Inflate(rect, 4, 4).Contains(clickPt) && rect.Contains(clickPt) == false)
					{
						// Yep, clicked on the frame.
						this.mouseDownOnSelectedFrame = true;
						this.selectedFrameClickOffset = new Point(this.selectedNode.Rect.X - clickPt.X, this.selectedNode.Rect.Y - clickPt.Y);
						return;
					}*/
				}

				this.Invalidate();
			}
		}




		protected override void OnMouseMove(MouseEventArgs e)
		{
			base.OnMouseMove(e);


			Point clickPt = e.Location;
			clickPt.Offset(this.hScrollBar.Value, this.vScrollBar.Value);


			// Drag handle?
			if (e.Button == MouseButtons.Left)
			{
				this.Cursor = Cursors.Default;
				if (this.drawNode != null)
				{
					if (this.drawNode is IRectangularNode)
					{
						var node = this.drawNode as IRectangularNode;
						node.Rectangle = RectangleF.FromLTRB(this.lastMouseDownPoint.X, this.lastMouseDownPoint.Y, clickPt.X, clickPt.Y);
					}
					else if (this.drawNode is Line)
					{
						var node = this.drawNode as Line;
						node.Pt1 = this.lastMouseDownPoint;
						node.Pt2 = clickPt;
					}
					this.drawNode.RelocateGrabHandles();

					this.Invalidate();
					return;
				}


				if (focusHandle != null)
				{
					Point newPt = e.Location;
					float angle = 0f;
					float gdiAngle = 0f;
					newPt.Offset(this.mouseDownOffset);
					if (focusHandleMoveBehaviour == HandleMoveBehavior.All)
					{
						this.focusHandle.Location = newPt;
					}
					else if (focusHandleMoveBehaviour == HandleMoveBehavior.Horizontally)
					{
						// only change X
						this.focusHandle.Location = new Point(newPt.X, this.focusHandle.Location.Y);
					}
					else if (focusHandleMoveBehaviour == HandleMoveBehavior.Vertically)
					{
						// only change Y
						this.focusHandle.Location = new Point(this.focusHandle.Location.X, newPt.Y);
					}
					else if (focusHandleMoveBehaviour == HandleMoveBehavior.NESW)
					{
						// change X and adapt Y
						int diff = this.focusHandle.Location.X - newPt.X;
						this.focusHandle.Location = new Point(newPt.X, this.focusHandle.Location.Y + diff);
					}
					else if (focusHandleMoveBehaviour == HandleMoveBehavior.NWSE)
					{
						// change Y and adapt X
						int diff = this.focusHandle.Location.X - newPt.X;
						this.focusHandle.Location = new Point(newPt.X, this.focusHandle.Location.Y - diff);
					}
					else if (focusHandleMoveBehaviour == HandleMoveBehavior.NodeCircular)
					{
						// calc the new position according to the node's rect.
						var rect = this.focusHandle.Node.Rect;
						Point origo = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
						SizeF diffRect = new SizeF(newPt.X - origo.X, newPt.Y - origo.Y);
						float coeff = rect.Width / Convert.ToSingle(rect.Height);
						//diffRect.Width /= coeff;
						if (!diffRect.IsEmpty)
						{
							double angleX = Math.Atan((diffRect.Width / coeff) / diffRect.Height);
							double angleGDI = Math.Atan(diffRect.Width / diffRect.Height);
							double sin = Math.Sin(angleX);
							double cos = Math.Cos(angleX);

							if (diffRect.Height < 0)
							{
								sin = -sin;
								cos = -cos;
							}
							PointF newCircPt = new PointF(
								Convert.ToSingle(sin * (rect.Width / 2)),
								Convert.ToSingle(cos * (rect.Height / 2))
							);
							newCircPt.X += origo.X;
							newCircPt.Y += origo.Y;
							this.focusHandle.Location = newCircPt.ToPoint();

							// calc the degree for the canvas.
							float angleSin = Convert.ToSingle(Math.Abs(angleX) * (180 / Math.PI));
							float angleCos = Convert.ToSingle(90 - angleSin);
							if (diffRect.Width < 0 && diffRect.Height < 0)
								angle = angleCos + 180;
							else if (diffRect.Width < 0)
								angle = angleSin + 90;
							else if (diffRect.Height < 0)
								angle = angleSin + 270;
							else
								angle = angleSin;

							// calc the degree for gdi+
							float angleGIDSin = Convert.ToSingle(Math.Abs(angleGDI) * (180 / Math.PI));
							float angleGIDCos = Convert.ToSingle(90 - angleSin);
							if (diffRect.Width < 0 && diffRect.Height < 0)
								gdiAngle = 90-angleGIDSin + 180;
							else if (diffRect.Width < 0)
								gdiAngle = angleGIDSin + 90;
							else if (diffRect.Height < 0)
								gdiAngle = angleGIDSin + 270;
							else
								gdiAngle = 90-angleGIDSin;

							//Debug.WriteLine(string.Format("aa {0:##0}:{1:##0} ANGLE: {2:##0}", angleSin, angleCos, angle));

						}
					}
					this.focusHandle.Node.OnGrabHandleMove(new HandleMovedEventArgs(this.focusHandle, angle, gdiAngle));
					this.Invalidate();
				}
				else if (this.mouseDownOnSelectedFrame && this.selectedNode != null)
				{
					Point newLoc = clickPt;
					Point pt = this.selectedNode.Rect.Location;
					newLoc.Offset(this.selectedFrameClickOffset);
					newLoc.X = Math.Max(newLoc.X, 0);
					newLoc.Y = Math.Max(newLoc.Y, 0);
					NodeMovedEventArgs args = new NodeMovedEventArgs(
						newLoc,
						new Size(newLoc.X - pt.X, newLoc.Y - pt.Y)
					);
					this.selectedNode.OnMoved(args);
					this.Invalidate();

				}
			}


			if (e.Button == MouseButtons.None && this.selectedNode != null)
			{
				// Check for over handles
				var handle = this.selectedNode.GetHandleAtPoint(clickPt);
				if (handle != null)
				{
					// change cursor.
					switch (handle.MoveBehavior)
					{
						case HandleMoveBehavior.Horizontally: this.Cursor = Cursors.SizeWE; break;
						case HandleMoveBehavior.Vertically: this.Cursor = Cursors.SizeNS; break;
						case HandleMoveBehavior.NWSE: this.Cursor = Cursors.SizeNWSE; break;
						case HandleMoveBehavior.NESW: this.Cursor = Cursors.SizeNESW; break;
						default: this.Cursor = Cursors.Default; break;
					}
				}
				else
				{
					this.Cursor = Cursors.Default;
				}
			}
			else
			{
				this.Cursor = Cursors.Default;
			}
		}

		protected override void OnMouseUp(MouseEventArgs e)
		{
			base.OnMouseUp(e);

			this.mouseDownOnSelectedFrame = false;
			this.focusHandle = null;


			Point clickPt = e.Location;
			clickPt.Offset(this.hScrollBar.Value, this.vScrollBar.Value);

			// Check for drawing a node.
			if (e.Button == MouseButtons.Left && this.drawNode != null)
			{
				// add the node to the collection and uncharge it.
				if (this.drawNode is IRectangularNode)
				{
					((IRectangularNode)this.drawNode).Rectangle = ((IRectangularNode)this.drawNode).Rectangle.Absolute();
					this.drawNode.RelocateGrabHandles();
				}
				this.AddNode(this.drawNode);
				this.SelectedNode = this.drawNode;
				this.drawNode = null;
				this.Invalidate();
				return;
			}


			// Check if click
			if (e.Button == MouseButtons.Left && clickPt == this.lastMouseDownPoint)
			{
				// check for selects
				if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right)
				{
					// Hit test grabhandles.
					Node nodeToSelect = null;
					foreach (var node in this.nodes)
					{
						if (node.PointIsOnNode(clickPt))
						{
							nodeToSelect = node;
							break;
						}
					}
					// just to invoke events.
					this.SelectedNode = nodeToSelect;
					this.Invalidate();
				}
			}

		}


		public string CreateCode()
		{
			var uiCulture = Thread.CurrentThread.CurrentUICulture;
			Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

			StringBuilder sb = new StringBuilder();
			sb.Append(@"
	GraphicsPath gp = new GraphicsPath();");
			foreach (var node in this.nodes)
			{
				sb.Append(node.CreateCode());
			}
			sb.Append(Environment.NewLine).Append(Environment.NewLine);

			Thread.CurrentThread.CurrentUICulture = uiCulture;

			return sb.ToString();
		}


		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);

			if (this.vScrollBar != null && this.hScrollBar != null)
			{
				// Set size and position of the scrollbars.
				this.vScrollBar.SuspendLayout();
				this.vScrollBar.Location = new Point(this.Width - this.vScrollBar.Width-1, 1);
				this.vScrollBar.Size = new Size(this.vScrollBar.Width, this.Height - ((this.hScrollBar.Visible) ? this.hScrollBar.Height : 2));
				this.vScrollBar.ResumeLayout();

				this.hScrollBar.SuspendLayout();
				this.hScrollBar.Location = new Point(1, this.Height - this.hScrollBar.Height-1);
				this.hScrollBar.Size = new Size(this.Width - ((this.vScrollBar.Visible) ? this.vScrollBar.Width : 2), this.hScrollBar.Height);
				this.hScrollBar.ResumeLayout();
				this.ResumeLayout();
			}

			this.Invalidate();
		}



		Point lastPasteLocation = Point.Empty;



		protected override void OnKeyDown(KeyEventArgs e)
		{
			base.OnKeyDown(e);


			// Delete
			if (e.KeyData == Keys.Delete && this.selectedNode != null)
			{
				this.selectedNode.Remove();
				return;
			}


			// Copy
			if (e.KeyCode == Keys.C && e.Modifiers == Keys.Control)
			{
				if (this.selectedNode != null)
				{
					this.CopyNode = this.selectedNode.Clone() as Node;
					this.lastPasteLocation = Point.Empty;
				}
				return;
			}
			// Cut
			if (e.KeyCode == Keys.X && e.Modifiers == Keys.Control)
			{
				if (this.selectedNode != null)
				{
					this.CopyNode = this.selectedNode.Clone() as Node;
					this.lastPasteLocation = Point.Empty;
					this.selectedNode.Remove();
				}
				return;
			}

			// Paste
			if (e.KeyCode == Keys.V && e.Modifiers == Keys.Control)
			{
				if (this.selectedNode != null)
				{
					Node n = this.CopyNode.Clone() as Node;
					if (n != null)
					{
						if (n is IRectangularNode)
						{
							if (lastPasteLocation == Point.Empty)
							{
								this.lastPasteLocation = ((IRectangularNode)n).Rectangle.Location.ToPoint();
							}
							this.lastPasteLocation.Offset(10, 10);
							((IRectangularNode)n).Rectangle = new RectangleF(this.lastPasteLocation, ((IRectangularNode)n).Rectangle.Size);
						}
						this.AddNode(n);
						this.SelectedNode = n;
						n.RelocateGrabHandles();
					}
				}
				return;
			}

			// Move up, down, left, right
			if (e.KeyCode == Keys.Up && this.selectedNode != null)
			{
				// Add logic.
			}
			if (e.KeyCode == Keys.Down && this.selectedNode != null)
			{
				// Add logic.
			}
			if (e.KeyCode == Keys.Left && this.selectedNode != null)
			{
				// Add logic.
			}
			if (e.KeyCode == Keys.Right && this.selectedNode != null)
			{
				// Add logic.
			}

		}



		protected override void OnMouseWheel(MouseEventArgs e)
		{
			base.OnMouseWheel(e);


			// Scroll.
			bool vertical = (Control.ModifierKeys != Keys.Shift);
			if (vertical && this.vScrollBar.Enabled)
			{
				int newValue = this.vScrollBar.Value + Convert.ToInt32(-e.Delta * 0.4f);
				this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum - this.vScrollBar.LargeChange, newValue));
			}
			else if (!vertical && this.hScrollBar.Enabled)
			{
				int newValue = this.hScrollBar.Value + Convert.ToInt32(-e.Delta * 1.0f);
				this.hScrollBar.Value = Math.Max(0, Math.Min(this.hScrollBar.Maximum - this.hScrollBar.LargeChange, newValue));
			}
		}



		public void Clear()
		{
			this.nodes.Clear();
			this.focusHandle = null;
			this.selectedNode = null;
			this.hScrollBar.Value = 0;
			this.vScrollBar.Value = 0;
			if (this.NodeListCleared != null)
				this.NodeListCleared(this, new EventArgs());
			this.Invalidate();
		}




		public SizeF Extent
		{
			get
			{
				// Determine the extent size.
				float maxX = 0f;
				float maxY = 0f;
				this.nodes.ForEach(new Action<Node>((n) =>
					{
						maxX = Math.Max(maxX, n.Rect.Right);
						maxY = Math.Max(maxY, n.Rect.Bottom);
					}));
				return new SizeF(maxX, maxY);
			}
		}



	}


	public class PaintNodeEventArgs : EventArgs
	{
		public PaintNodeEventArgs(Node node, Graphics graphics)
		{
			this.Graphics = graphics;
			this.Node = node;
		}
		public Graphics Graphics { get; set; }
		public Node Node { get; set; }
	}

	public class NodeMovedEventArgs : EventArgs
	{
		public NodeMovedEventArgs(Point newLocation, Size difference)
		{
			this.NewLocation = newLocation;
			this.Difference = difference;
		}
		public Point NewLocation { get; set; }
		public Size Difference { get; set; }
	}


	public class NodeListChangedEventArgs : EventArgs
	{
		public NodeListChangedEventArgs(Node node)
		{
			this.Node = node;
		}
		public Node Node { get; set; }
	}

	public class SelectedNodePropertyChanged : EventArgs
	{
		public SelectedNodePropertyChanged(Node node)
		{
			this.Node = node;
		}
		public Node Node { get; set; }
	}



}
